/**
 * @file STL_Serial.cpp
 * @author Derek Liu (yjkhtddx@sina.com)
 * @brief 
 * @version 0.1
 * @date 2021-03-25
 * 
 * @copyright Copyright Sci-Tech Laboratories (c) 2021
 * 
 */

#include "STL_Serial.h"
#include <sys/epoll.h>
#include <sys/socket.h>
#include <functional>
#include <thread>
#include <stdio.h>
#include <errno.h>
#include <stdint.h>
#include <fcntl.h>
#include <stropts.h>
#include <memory>
#include "STLOG.h"
#include <sys/select.h>
#include <unistd.h>
#include <sys/file.h>
#include <termios.h>

#define EPOLL_MAX_EVENTS 10 // 单次处理事件个数
namespace STL
{
    STL_Serial::STL_Serial(SerialConfig config) : m_config(config), m_serial_fd(-1), m_epoll_fd(-1), m_run(false)
    {
    }

    STL_Serial::~STL_Serial()
    {
        if (m_serial_fd != -1)
        {
            flock(m_serial_fd, LOCK_UN);
            STL_SAFE_CLOSE(m_serial_fd);
        }
        if (m_epoll_fd != -1)
        {
            STL_SAFE_CLOSE(m_epoll_fd);
        }
    }

    void STL_Serial::setnonblocking(int sock)
    {
        int opts;
        opts = fcntl(sock, F_GETFL);
        if (opts < 0)
        {
            perror("fcntl(sock,GETFL)");
            // exit(1);
        }
        opts = opts | O_NONBLOCK;
        if (fcntl(sock, F_SETFL, opts) < 0)
        {
            perror("fcntl(sock,SETFL,opts)");
            // exit(1);
        }
    }

    int STL_Serial::init(uint32_t const attempts)
    {
        std::string device = m_config.line;

        stlog_info("Trying to open serial port %s (attempt %d)\n", device.c_str(), attempts);

        m_serial_fd = open(device.c_str(), O_RDWR | O_NOCTTY | O_NONBLOCK);

        if (-1 == m_serial_fd)
        {
            stlog_error("ERROR: Failed to open serial port %s\n", device.c_str());
            STL_SAFE_CLOSE(m_serial_fd);
            return -1;
        }

        if (flock(m_serial_fd, LOCK_EX | LOCK_NB) != 0)
        {
            stlog_error("ERROR: Cannot get exclusive lock for serial port %s. Error code %d\n", device.c_str(), errno);
            stlog_error("ERROR: Failed to open serial port %s\n", device.c_str());
            STL_SAFE_CLOSE(m_serial_fd);
            return -1;
        }

        // TODO 这里编译不过
        // int bits;
        // bits = 0;
        // ioctl(m_serial_fd, TIOCMSET, &bits);

        // Configure the serial device parameters
        // Build on the current configuration
        struct termios tios;

        bzero(&tios, sizeof(tios));
        tcgetattr(m_serial_fd, &tios);
        switch (m_config.parity)
        {
        case STL_Serial::Parity_None:
            tios.c_iflag = IGNPAR;
            break;
        case STL_Serial::Parity_Odd:
            tios.c_iflag = INPCK;
            tios.c_cflag = PARENB | PARODD;
            break;
        default:
            stlog_error("ERROR: Parity not supported\n");
            stlog_error("ERROR: Failed to open serial port %s\n", device.c_str());
            STL_SAFE_CLOSE(m_serial_fd);
            return -1;
        }
        switch (m_config.stopBits)
        {
        case STL_Serial::StopBits_One:
            break; // default
        case STL_Serial::StopBits_Two:
            tios.c_cflag |= CSTOPB;
            break;
        default:
            stlog_error("ERROR: Stopbits not supported\n");
            stlog_error("ERROR: Failed to open serial port %s\n", device.c_str());
            STL_SAFE_CLOSE(m_serial_fd);
            return -1;
        }
        tios.c_iflag |= IGNBRK;
        tios.c_cflag |= CS8 | CREAD | CLOCAL;
        tios.c_oflag = 0;
        tios.c_lflag = 0;
        for (int i = 0; i < NCCS; i++)
            tios.c_cc[i] = 0;
        tios.c_cc[VMIN] = 0;
        tios.c_cc[VTIME] = 1;
        switch (m_config.baud)
        {
        case 300:
            cfsetspeed(&tios, B300);
            break;
        case 1200:
            cfsetspeed(&tios, B1200);
            break;
        case 2400:
            cfsetspeed(&tios, B2400);
            break;
        case 4800:
            cfsetspeed(&tios, B4800);
            break;
        case 9600:
            cfsetspeed(&tios, B9600);
            break;
        case 19200:
            cfsetspeed(&tios, B19200);
            break;
        case 38400:
            cfsetspeed(&tios, B38400);
            break;
        case 57600:
            cfsetspeed(&tios, B57600);
            break;
        // case 76800:
        //     cfsetspeed(&tios, B76800);
        //     break;
        case 115200:
            cfsetspeed(&tios, B115200);
            break;
        case 230400:
            cfsetspeed(&tios, B230400);
            break;
        default:
            stlog_error("Baud rate not supported");
            stlog_error("ERROR: Failed to open serial port %s\n", device.c_str());
            STL_SAFE_CLOSE(m_serial_fd);
            return -1;
        }
        if (tcsetattr(m_serial_fd, TCSANOW, &tios) == -1)
        {
            // Error.  Clean up and exit
            stlog_error("ERROR: Failed to set serial port parameters");
            stlog_error("ERROR: Failed to open serial port %s\n", device.c_str());
            STL_SAFE_CLOSE(m_serial_fd);
            return -1;
        }

        tcflush(m_serial_fd, TCIOFLUSH);

        // Open successful
        stlog_info("Serial port %s opened (attempt %d)", device.c_str(), attempts);
        return 0;
    }

    int STL_Serial::openSerial()
    {
        if (init(1))
        {
            return -1;
        }

        m_run = true;
        setStatus(STL_Serial::Status::Init);
        m_thread = std::thread(std::bind(&STL_Serial::serialThread, this));

        return 0;
    }
    int STL_Serial::closeSerial()
    {
        m_run = false;
        if (m_thread.joinable())
        {
            m_thread.join();
        }

        if (m_serial_fd != -1)
        {
            flock(m_serial_fd, LOCK_UN);
            STL_SAFE_CLOSE(m_serial_fd);
        }
    }

    void STL_Serial::onData(int socket)
    {
        stlog_trace("STL_Serial::onData\n");

        size_t buflen;
        long sizeRet = recv(socket, m_buffer, STL_Serial_Buffer_Max, 0);
        int ierrno = errno;
        if (sizeRet == 0 || (sizeRet < 0 && (ierrno != EAGAIN && ierrno != EWOULDBLOCK && ierrno != EINTR)))
        {
            // perror("socket recv");
            stlog_trace("sizeRet:%ld---ierrno:%d \n", sizeRet, ierrno);
            onDisconnect(socket);
            STL_SAFE_CLOSE(socket);
            return;
        }
        std::shared_ptr<std::string> sp_data(new std::string((char *)m_buffer, (size_t)sizeRet));
        stlog_trace("STL_Serial::recvData:\n%s\n", sp_data->c_str());
        if (m_DataCallback)
            m_DataCallback(sp_data);
    }

    void STL_Serial::onConnect(int socket, std::string ip, uint16_t port)
    {
        stlog_trace("STL_Serial::onConnect\n");

        stlog_trace("input [SOCKET: %d, IP：%s,  PORT：%d]\n", socket, ip.c_str(), port);
        // m_socketMap[socket] = std::shared_ptr<SocketInfo>(new SocketInfo(socket, ip, port));
        // if (m_ConnectCallback)
        //     m_ConnectCallback(socket, ip, port);
    }
    void STL_Serial::onDisconnect(int socket)
    {
        stlog_trace("STL_Serial::onDisconnect\n");
        // if (m_socketMap.count(socket) == 1)
        // {
        //     m_socketMap.erase(socket);
        //     if (m_DisconnectCallback)
        //         m_DisconnectCallback(socket);
        // }
    }

    void STL_Serial::sendData(int socket, std::string &data)
    {
        stlog_trace("STL_Serial::sendData\n%s\n", data.c_str());
        // if (m_socketMap.count(socket) == 1)
        // {
        //     //TODO 发送参考haicams项目，
        //     send(socket, data.data(), data.size(), 0);
        // }
    }

    void STL_Serial::closeSocket(int socket)
    {
        // if (m_socketMap.count(socket) == 1)
        // {
        //     close(socket);
        // }
    }

    void STL_Serial::serialThread()
    {
        memset(m_buffer, 0, STL_Serial_Buffer_Max);
        do
        {
            int bytesRead;
            int err;
            do
            {
                bytesRead = read(m_serial_fd, m_buffer, STL_Serial_Buffer_Max);
                if (bytesRead > 0)
                {
                    std::shared_ptr<std::string> sp_data(new std::string((char *)m_buffer, (size_t)bytesRead));
                    stlog_trace("STL_TcpServer::recvData:\n%s\n", sp_data->c_str());
                    if (m_DataCallback)
                        m_DataCallback(sp_data);
                }
            } while (bytesRead > 0);
            do
            {
                struct timeval *whenp;
                fd_set rds, eds;
                int oldstate;
                FD_ZERO(&rds);
                FD_SET(m_serial_fd, &rds);
                FD_ZERO(&eds);
                FD_SET(m_serial_fd, &eds);
                whenp = NULL;
                err = select(m_serial_fd + 1, &rds, NULL, &eds, whenp);
            } while (err <= 0);
        } while (m_run);
    }
} // namespace STL